package com.zxy.supplier_system.server.service

import com.zxy.supplier_system.server.controller.MarketAccountsSaleDocumentController
import com.zxy.supplier_system.server.controller.MarketSaleDocumentController
import com.zxy.supplier_system.server.controller.MarketSaleDocumentsItemController
import com.zxy.supplier_system.server.entity.*
import com.zxy.supplier_system.server.repository.*
import com.zxy.supplier_system.server.utils.IntIdAndName
import com.zxy.supplier_system.server.utils.LoggerDelegate
import com.zxy.supplier_system.server.utils.orElse404
import com.zxy.supplier_system.server.utils.toIntIdAndName
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.data.jpa.domain.Specification
import org.springframework.data.repository.findByIdOrNull
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.server.ResponseStatusException
import java.time.OffsetDateTime
import java.time.YearMonth
import java.util.*

@Service
class MarketSaleDocumentService(
    private val marketSaleDocumentRepository: MarketSaleDocumentRepository,
    private val marketRepository: MarketRepository,
    private val marketSaleDocumentItemRepository: MarketSaleDocumentItemRepository,
    private val employeeRepository: EmployeeRepository,
    private val systemServiceService: SystemServiceService,
    private val goodsRepository: GoodsRepository,
    private val employeeService: EmployeeService,
    private val employeeOperateRecordService: EmployeeOperateRecordService,
    private val marketAccountRepository: MarketAccountRepository,
    private val marketInternalGoodsCodeRepository: MarketInternalGoodsCodeRepository,
) {

    companion object {
        val log by LoggerDelegate()
    }

    @Transactional(readOnly = true)
    fun queryMarketSaleDocument(
        queryMarketSaleDocumentRequest: MarketSaleDocumentController.QueryMarketSaleDocumentRequest,
        pageable: Pageable
    ): Page<MarketSaleDocumentController.QueryMarketSaleDocumentResponse> {

        val employee = employeeService.getRequiredCurrentEmployee()
        return this.marketSaleDocumentRepository.findAll(
            MarketSaleDocumentRepository.Specifications.fromRequest(queryMarketSaleDocumentRequest).and(
                if (employee.authorities.contains(Authority.卖场_订单_查询所有)) {
                    Specification.where(null)
                } else {
                    MarketSaleDocumentRepository.Specifications.marketSaleManagerEqual(employee.id!!)
                        .or(MarketSaleDocumentRepository.Specifications.marketShoppingGuidesContains(employee.id!!))
                }
            ), pageable
        ).map { marketSaleDocument ->
            MarketSaleDocumentController.QueryMarketSaleDocumentResponse(
                marketSaleDocument.id,
                marketSaleDocument.createdBy.let {
                    IntIdAndName(it.id!!, it.name)
                },
                marketSaleDocument.createdDateTime,
                marketSaleDocument.goodsList.size,
                marketSaleDocument.market.let {
                    IntIdAndName(it.id, it.name)
                },
                marketSaleDocument.status,
                marketSaleDocument.month,
                marketSaleDocument.year,
            )
        }
    }

    @Transactional
    fun createMarketSaleDocument(
        accountId: Int,
        request: MarketAccountsSaleDocumentController.CreateMarkerReturnOrderRequest
    ): Long {
        val marketSaleDocument = this.marketSaleDocumentRepository.save(MarketSaleDocument().apply {
            this.note = request.note
            this.year = request.year
            this.month = request.month
            this.account = marketAccountRepository.findByIdOrNull(accountId).orElse404()
        })

        this.employeeOperateRecordService.createMarketSaleDocument(marketSaleDocument)
        return marketSaleDocument.id
    }

    @Transactional
    fun updateOrderGoods(
        id: Long,
        updateOrderGoodsRequest: MarketSaleDocumentController.UpdateOrderGoodsRequest
    ) {
        val marketSaleDocument = this.marketSaleDocumentRepository.findByIdOrNull(id).orElse404()
        marketSaleDocument.goodsList.clear()
        marketSaleDocument.goodsList.addAll(
            updateOrderGoodsRequest.goodsList.mapIndexed { index, it ->
                MarketSaleDocumentItem().apply {
                    this.document = marketSaleDocument
                    this.amount = it.amount
                    this.goods =
                        goodsRepository.findByIdAndSystemServiceAndDisableIsFalse(it.id, systemServiceService.getCurrentSystemService())
                            .orElse404()
                    this.sequence = index
                }
            }
        )
        this.marketSaleDocumentRepository.save(marketSaleDocument)
    }


    @Transactional(readOnly = true)
    fun getMarketSaleDocumentDetail(
        accountId: Int,
        year: Int,
        month: Int,
        includeItems: Boolean
    ): MarketSaleDocumentController.MarketSaleDocumentDetail {
        val marketSaleDocument =
            this.marketSaleDocumentRepository.findByAccount_IdAndYearAndMonthAndAccount_Market_SystemService(
                accountId,
                year,
                month,
                systemServiceService.getCurrentSystemService()
            ).orElse404()
        return MarketSaleDocumentController.MarketSaleDocumentDetail.fromMarketSaleDocument(
            marketSaleDocument,
            includeItems
        )
    }

    @Transactional(readOnly = true)
    fun getMarketSaleDocumentDetail(
        id: Long,
        includeItems: Boolean
    ): MarketSaleDocumentController.MarketSaleDocumentDetail {
        val marketSaleDocument = this.marketSaleDocumentRepository.findByIdOrNull(id).orElse404()
        return MarketSaleDocumentController.MarketSaleDocumentDetail(
            marketSaleDocument.id,
            marketSaleDocument.createdDateTime,
            IntIdAndName(marketSaleDocument.createdBy.id!!, marketSaleDocument.createdBy.name),
            IntIdAndName(marketSaleDocument.market.id, marketSaleDocument.market.name),
            if (!includeItems) emptyList() else marketSaleDocument.goodsList.map { item ->
                val goods = item.goods
                goods.let { it ->
                    MarketSaleDocumentController.MarketSaleDocumentItemResponse(
                        it.id,
                        it.name, it.barcode,
                        it.price,
                        it.amount,
                        it.createdDateTime,
                        it.lastModifiedDateTime,
                        it.brand?.toIntIdAndName(),
                        it.category?.toIntIdAndName(),
                        it.factory?.toIntIdAndName(),
                        item.amount,
                        it.boxSpecification,
                        item.totalFee,
                    )
                }
            },
            marketSaleDocument.submittedDateTime,
            marketSaleDocument.auditedDateTime,
            marketSaleDocument.month,
            marketSaleDocument.year,
            marketSaleDocument.account.let { it ->
                IntIdAndName(it.id!!, it.name)
            },
            marketSaleDocument.note,
        )
    }

    @Transactional
    fun addItem(
        documentId: Long,
        addMarketSaleDocumentsItemRequest: MarketSaleDocumentsItemController.AddMarketSaleDocumentsItemRequest
    ) {
        val marketSaleDocument = this.marketSaleDocumentRepository.findByIdOrNull(documentId).orElse404()
        marketSaleDocument.goodsList.add(MarketSaleDocumentItem().apply {
            this.document = marketSaleDocument
            this.goods = goodsRepository.findByIdAndSystemServiceAndDisableIsFalse(
                addMarketSaleDocumentsItemRequest.goodsId,
                systemServiceService.getCurrentSystemService()
            ).orElse404()
            this.amount = addMarketSaleDocumentsItemRequest.amount
            this.sequence = marketSaleDocument.goodsList.nextSequence
        })
        this.marketSaleDocumentRepository.save(marketSaleDocument)
        this.employeeOperateRecordService.addMarketSaleDocumentItem(marketSaleDocument)
    }

    @Transactional
    fun deleteItem(documentId: Long, goodsId: Int) {

        val marketSaleDocumentItem =
            this.marketSaleDocumentItemRepository.findByIdOrNull(MarketSaleDocumentItem.UnionId().apply {
                this.document = documentId
                this.goods = goodsId
            })
        if (marketSaleDocumentItem != null) {
            this.marketSaleDocumentItemRepository.delete(marketSaleDocumentItem)
            this.employeeOperateRecordService.deleteMarketSaleDocumentItem(marketSaleDocumentItem)
        }
    }

    @Transactional
    fun updateItemAmount(
        goodsId: Int,
        documentId: Long,
        updateItemAmountRequest: MarketSaleDocumentsItemController.UpdateItemAmountRequest
    ) {
        val marketSaleDocumentItem =
            this.marketSaleDocumentItemRepository.findByIdOrNull(MarketSaleDocumentItem.UnionId().apply {
                this.document = documentId
                this.goods = goodsId
            }).orElse404()
        marketSaleDocumentItem.amount = updateItemAmountRequest.amount
        this.marketSaleDocumentItemRepository.save(marketSaleDocumentItem)
        this.employeeOperateRecordService.updateMarketSaleDocumentItemAmount(marketSaleDocumentItem)
    }

    fun getAllEmployees(): List<IntIdAndName> {
        return this.employeeRepository.findAll(
            EmployeeRepository.Specifications.haveMarketSaleDocument().and(
                SystemServiceRepository.Specifications.systemServiceEquals(
                    { root -> root },
                    systemServiceService.getCurrentSystemService()
                )
            )
        ).map {
            IntIdAndName(it.id!!, it.name)
        }
    }

    @Transactional
    fun queryMarketSaleDocumentInAccount(
        accountId: Int,
        pageable: Pageable,
        request: MarketSaleDocumentController.QueryMarketSaleDocumentRequest
    ): Page<MarketAccountsSaleDocumentController.QueryMarketSaleDocumentInMarketResponse> {
        return this.marketSaleDocumentRepository.findAll(
            MarketSaleDocumentRepository.Specifications.fromRequest(request).and(
                MarketSaleDocumentRepository.Specifications.accountIn(
                    listOf(accountId)
                )
            ), pageable
        ).map {
            MarketAccountsSaleDocumentController.QueryMarketSaleDocumentInMarketResponse(
                it.id,
                it.createdBy.toIntIdAndName(),
                it.createdDateTime,
                it.goodsList.size,
                it.submittedDateTime,
                it.status,
                it.year,
                it.month,
            )
        }
    }

    fun queryOrderItemsInOrder(
        documentId: Long,
        pageable: Pageable
    ): Page<MarketSaleDocumentsItemController.OrderItemInOrderSummary> {
        return this.marketSaleDocumentItemRepository.findAllByDocument_IdOrderBySequenceAsc(documentId, pageable).map {
            val goods = it.goods
            MarketSaleDocumentsItemController.OrderItemInOrderSummary.fromGoodsAndAmount(goods, it.amount)
        }
    }

    fun exists(documentId: Long, goodsId: Int): Boolean {
        return this.marketSaleDocumentItemRepository.existsByDocument_IdAndGoods_Id(documentId, goodsId)
    }

    fun exists(documentId: Long, barcode: String): Boolean {
        return this.marketSaleDocumentItemRepository.existsByDocument_IdAndGoods_Barcode(documentId, barcode)
    }


    @Transactional
    fun addItem(documentId: Long, barcode: String): MarketSaleDocumentsItemController.OrderItemInOrderSummary {
        val currentSystemService = systemServiceService.getCurrentSystemService()
        val marketSaleDocument = this.marketSaleDocumentRepository.findByIdAndAccount_Market_SystemService(
            documentId,
            currentSystemService
        ).orElse404()
        val goods =
            goodsRepository.findByBarcodeAndSystemServiceAndDisableIsFalse(barcode, currentSystemService)
                .orElse404()
        return orderItemInOrderSummary(marketSaleDocument, goods)
    }

    @Transactional
    fun submitMarketSaleDocument(id: Long) {
        val marketSaleDocument = this.marketSaleDocumentRepository.findByIdAndAccount_Market_SystemService(
            id,
            systemServiceService.getCurrentSystemService()
        ).orElse404()
        if (marketSaleDocument.submittedDateTime == null) {
            marketSaleDocument.submittedDateTime = OffsetDateTime.now()
            this.marketSaleDocumentRepository.save(marketSaleDocument)
            this.employeeOperateRecordService.submitMarketSaleDocument(marketSaleDocument)
            // TODO notify
        } else {
            throw ResponseStatusException(HttpStatus.BAD_REQUEST)
        }
    }

    @Transactional
    fun addItem(documentId: Long, goodsId: Int): MarketSaleDocumentsItemController.OrderItemInOrderSummary {
        val currentSystemService = systemServiceService.getCurrentSystemService()
        val marketSaleDocument = this.marketSaleDocumentRepository.findByIdAndAccount_Market_SystemService(
            documentId,
            currentSystemService
        ).orElse404()
        val goods =
            goodsRepository.findByIdAndSystemServiceAndDisableIsFalse(goodsId, currentSystemService)
                .orElse404()
        return orderItemInOrderSummary(marketSaleDocument, goods)
    }

    private fun orderItemInOrderSummary(
        marketSaleDocument: MarketSaleDocument,
        goods: Goods
    ): MarketSaleDocumentsItemController.OrderItemInOrderSummary {
        val amount = 1
        marketSaleDocument.goodsList.add(MarketSaleDocumentItem().apply {
            this.document = marketSaleDocument
            this.goods =
                goods
            this.amount = amount
            this.sequence = marketSaleDocument.goodsList.nextSequence
        })
        this.marketSaleDocumentRepository.save(marketSaleDocument)
        this.employeeOperateRecordService.addMarketSaleDocumentItem(marketSaleDocument)
        return MarketSaleDocumentsItemController.OrderItemInOrderSummary.fromGoodsAndAmount(goods, amount)
    }

    @Transactional
    fun audit(id: Long): MarketSaleDocument {
        val marketSaleDocument = this.marketSaleDocumentRepository.findByIdAndAccount_Market_SystemService(
            id,
            systemServiceService.getCurrentSystemService()
        ).orElse404()
        if (marketSaleDocument.submittedDateTime == null) {
            throw ResponseStatusException(HttpStatus.BAD_REQUEST, "订单未提交")
        }
        marketSaleDocument.goodsList.forEach { item ->
            val marketGoods = marketSaleDocument.market.goodsList.find {
                it.goods.id == item.goods.id
            }
            if (marketGoods != null) {
                marketGoods.amount -= item.amount
            } else {
                log.error("marketGoods not found ${item.goods.id} ${item.goods.name}")
            }
        }
        this.marketRepository.save(marketSaleDocument.market)
        for (item in marketSaleDocument.goodsList) {
            val goods = item.goods
            goods.amount += item.amount
            goodsRepository.save(goods)
        }
        marketSaleDocument.auditedDateTime = OffsetDateTime.now()
        this.employeeOperateRecordService.auditMarketSaleDocument(marketSaleDocument)
        return this.marketSaleDocumentRepository.save(marketSaleDocument)
    }

    @Transactional
    fun delete(id: Long) {
        val employee = this.employeeService.getRequiredCurrentEmployee()
        val marketSaleDocument =
            this.marketSaleDocumentRepository.findBy<MarketSaleDocument, Optional<MarketSaleDocument>>(
                Specification.allOf(MarketSaleDocumentRepository.Specifications.ifUCanSee(employee)).and(
                    MarketSaleDocumentRepository.Specifications.systemServiceEqual(
                        systemServiceService.getCurrentSystemService(),
                    ),
                ).and(MarketSaleDocumentRepository.Specifications.idEqual(id)).and(
                    MarketSaleDocumentRepository.Specifications.submittedDateTimeIsNull()
                )
            ) {
                it.one()
            }.orElse(null).orElse404()
        this.marketSaleDocumentRepository.delete(marketSaleDocument)
        this.employeeOperateRecordService.deleteMarketSaleDocument(marketSaleDocument)
    }

    fun queryOrderCreatedEmployeesByMarketId(marketId: Int): List<IntIdAndName> {
        return this.employeeRepository.findAll(
            EmployeeRepository.Specifications.haveMarketsSaleDocument(marketId).and(
                SystemServiceRepository.Specifications.systemServiceEquals(
                    { root -> root },
                    systemServiceService.getCurrentSystemService()
                )
            )
        ).map {
            IntIdAndName(it.id!!, it.name)
        }
    }

    @Transactional
    fun importFromExcel(
        accountId: Int, year: Int, month: Int,
        goodsItems: List<MarketSaleDocumentsItemController.SaleDocumentExcelItem>
    ) {
        val systemService = systemServiceService.getCurrentSystemService()
        val marketSaleDocument =
            this.marketSaleDocumentRepository.findByAccount_IdAndYearAndMonthAndAccount_Market_SystemService(
                accountId,
                year, month,
                systemService
            ).orElse404()
        importFromExcel(goodsItems, marketSaleDocument)
    }

    fun importFromExcel(
        goodsItems: List<MarketSaleDocumentsItemController.SaleDocumentExcelItem>,
        marketSaleDocument: MarketSaleDocument
    ) {
        val market = marketSaleDocument.market
        val internalGoodsCodes by
            lazy {
                marketInternalGoodsCodeRepository.findByMarketIdAndMarket_SystemService(
                    market.id,
                    systemServiceService.getCurrentSystemService()
                ).associate {
                    it.code to it.goods
                }
            }
        val goodsList = goodsItems.groupBy {
            it.barcode
        }.values.map { value->
            if (value.size>1){
                // One barcode multi goods number in market system
                MarketSaleDocumentsItemController.SaleDocumentExcelItem(value.first().barcode,value.sumOf { it.amount },value.sumOf { it.totalFee })
            }else{
                value.first()
            }
        }.filterNot { i -> marketSaleDocument.goodsList.any { it.goods.barcode == i.barcode } }
            .mapIndexed { index, it ->
                MarketSaleDocumentItem().apply {
                    totalFee = it.totalFee
                    sequence = marketSaleDocument.goodsList.nextSequence + 1 + index
                    goods = goodsRepository.findByBarcodeAndSystemServiceAndDisableIsFalse(
                        it.barcode,
                        systemServiceService.getCurrentSystemService()
                    )?:internalGoodsCodes[it.barcode]
                        ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "无法找到条码或店内码为${it.barcode}的商品")
                    amount = it.amount
                    document = marketSaleDocument
                }
            }
        marketSaleDocument.goodsList.addAll(goodsList)
        this.marketSaleDocumentRepository.save(marketSaleDocument)
    }

    @Transactional
    fun importFromExcel(documentId: Long, goodsItems: List<MarketSaleDocumentsItemController.SaleDocumentExcelItem>) {
        val systemService = systemServiceService.getCurrentSystemService()
        val marketSaleDocument =
            this.marketSaleDocumentRepository.findByIdAndAccount_Market_SystemService(
                documentId,
                systemService
            ).orElse404()
        importFromExcel(goodsItems, marketSaleDocument)
    }

    @Transactional
    fun move(
        goodsId: Int,
        accountId: Int,
        year: Int,
        month: Int,
        request: MarketAccountsSaleDocumentController.MoveGoodsRequest
    ) {
        val source =
            this.marketSaleDocumentItemRepository.findByDocument_Account_IdAndDocument_YearAndDocument_MonthAndGoods_IdAndGoods_SystemService(
                accountId,
                year,
                month,
                goodsId,
                systemServiceService.getCurrentSystemService(),
            ).orElse404()
        val document =
            this.marketSaleDocumentRepository.findByAccount_IdAndYearAndMonthAndAccount_Market_SystemService(
                request.targetAccountId,
                year,
                month,
                systemServiceService.getCurrentSystemService()
            ).orElse404()
        document.goodsList.add(MarketSaleDocumentItem().apply {
            this.document = document
            this.amount = source.amount
            this.goods = source.goods
            this.sequence = document.goodsList.nextSequence
            this.totalFee = source.totalFee
        })
        this.marketSaleDocumentRepository.save(document)
        this.marketSaleDocumentItemRepository.delete(source)
    }

    @Transactional
    fun move(
        accountId: Int,
        year: Int,
        month: Int,
        request: MarketAccountsSaleDocumentController.MoveMultiGoodsRequest
    ) {
        val sourceList =
            this.marketSaleDocumentItemRepository.findAllByDocument_Account_IdAndDocument_YearAndDocument_MonthAndGoods_SystemService(
                accountId,
                year,
                month,
                systemServiceService.getCurrentSystemService(),
            ).orElse404().filter {
                request.goodsIds.contains(it.goods.id)
            }
        val document =
            this.marketSaleDocumentRepository.findByAccount_IdAndYearAndMonthAndAccount_Market_SystemService(
                request.targetAccountId,
                year,
                month,
                systemServiceService.getCurrentSystemService()
            ).orElse404()
        sourceList.forEach { source->
            document.goodsList.add(MarketSaleDocumentItem().apply {
                this.document = document
                this.amount = source.amount
                this.goods = source.goods
                this.sequence = document.goodsList.nextSequence
                this.totalFee = source.totalFee
            })
        }
        this.marketSaleDocumentRepository.save(document)
        this.marketSaleDocumentItemRepository.deleteAll(sourceList)
    }

    @Transactional
    fun clear( accountId: Int,
               year: Int,
               month: Int,){
        val systemService = systemServiceService.getCurrentSystemService()
        this.marketSaleDocumentItemRepository.deleteAllByDocument_Account_IdAndDocument_YearAndDocument_MonthAndDocument_Account_Market_SystemService(accountId,year,month,systemService)
    }

    fun findDistinctYearMonthObjectByMarketId(marketId: Int): List<YearMonth> {
        return this.marketSaleDocumentRepository.findDistinctYearMonthObjectByMarket(marketId)
    }
}